package nachos.userprog;

import nachos.machine.*;
import nachos.threads.*;
import nachos.userprog.*;
import java.util.LinkedList;
import java.lang.Integer;

import java.io.EOFException;
import java.util.NoSuchElementException;


/**
 * Encapsulates the state of a user process that is not contained in its
 * user thread (or threads). This includes its address translation state, a
 * file table, and information about the program being executed.
 *
 * <p>
 * This class is extended by other classes to support additional functionality
 * (such as additional syscalls).
 *
 * @see	nachos.vm.VMProcess
 * @see	nachos.network.NetProcess
 */
public class UserProcess {
    /**
     * Allocate a new process.
     */
    public UserProcess() {
      if(UserKernel.processCount == 0){
			  pagesLock = new Lock();
			  pidLock = new Lock();
			  getIPTLock();
			  pidLock.acquire();
			  PID = UserKernel.uniquePID++;
			  UserKernel.processCount++;
			  pidLock.release();
		  }
		
	    fileDescriptor[0] = UserKernel.console.openForReading();
	    fileDescriptor[1] = UserKernel.console.openForWriting();
    }

    protected void getIPTLock(){
	// intentionally empty
    }

    protected void passIPTLock(UserProcess process){
	// intentionally empty
    }

    /**
     * Allocate and return a new process of the correct class. The class name
     * is specified by the <tt>nachos.conf</tt> key
     * <tt>Kernel.processClassName</tt>.
     *
     * @return	a new process of the correct class.
     */
    public static UserProcess newUserProcess() {
	    return (UserProcess)Lib.constructObject(Machine.getProcessClassName());
    }


    /**
     * Execute the specified program with the specified arguments. Attempts to
     * load the program, and then forks a thread to run it.
     *
     * @param	name	the name of the file containing the executable.
     * @param	args	the arguments to pass to the executable.
     * @return	<tt>true</tt> if the program was successfully executed.
     */
    public boolean execute(String name, String[] args) {
	    if (!load(name, args)){
	      return false;
      }
		
	    new UThread(this).setName(name).fork();

	    return true;
    }

    /**
     * Save the state of this process in preparation for a context switch.
     * Called by <tt>UThread.saveState()</tt>.
     */
    public void saveState() {
    }

    /**
     * Restore the state of this process after a context switch. Called by
     * <tt>UThread.restoreState()</tt>.
     */
    public void restoreState() {
	    Machine.processor().setPageTable(pageTable);
    }


    /**
     * Read a null-terminated string from this process's virtual memory. Read
     * at most <tt>maxLength + 1</tt> bytes from the specified address, search
     * for the null terminator, and convert it to a <tt>java.lang.String</tt>,
     * without including the null terminator. If no null terminator is found,
     * returns <tt>null</tt>.
     *
     * @param	vaddr	the starting virtual address of the null-terminated
     *			string.
     * @param	maxLength	the maximum number of characters in the string,
     *				not including the null terminator.
     * @return	the string read, or <tt>null</tt> if no null terminator was
     *		found.
     */
    public String readVirtualMemoryString(int vaddr, int maxLength) {
	    Lib.assertTrue(maxLength >= 0);

	    byte[] bytes = new byte[maxLength+1];

	    int bytesRead = readVirtualMemory(vaddr, bytes);

	    for (int length=0; length<bytesRead; length++) {
	        if (bytes[length] == 0)
		        return new String(bytes, 0, length);
	    }

	    return null;
    }


    /**
     * Transfer data from this process's virtual memory to all of the specified
     * array. Same as <tt>readVirtualMemory(vaddr, data, 0, data.length)</tt>.
     *
     * @param	vaddr	the first byte of virtual memory to read.
     * @param	data	the array where the data will be stored.
     * @return	the number of bytes successfully transferred.
     */
    public int readVirtualMemory(int vaddr, byte[] data) {
    	 return readVirtualMemory(vaddr, data, 0, data.length);
    }


    /**
     * Transfer data from this process's virtual memory to the specified array.
     * This method handles address translation details. This method must
     * <i>not</i> destroy the current process if an error occurs, but instead
     * should return the number of bytes successfully copied (or zero if no
     * data could be copied).
     *
     * @param	vaddr	the first byte of virtual memory to read.
     * @param	data	the array where the data will be stored.
     * @param	offset	the first byte to write in the array.
     * @param	length	the number of bytes to transfer from virtual memory to
     *			the array.
     * @return	the number of bytes successfully transferred.
     */
    public int readVirtualMemory(int vaddr, byte[] data, int offset,
		                             int length) {
	    Lib.assertTrue(offset >= 0 && length >= 0 
                     && offset+length <= data.length);
      System.out.println("calling readVirtualMemory");
      System.out.println(PID+" Kernel access to vpn: " + vaddr/pageSize);

      checkAddr(vaddr/pageSize, false);
      pinPage(vaddr);
      
      //System.out.println("Got to readVirtualMem");
	    byte[] memory = Machine.processor().getMemory();
	
	    // for now, just assume that virtual addresses equal physical addresses
	    if (vaddr < 0 || vaddr / pageSize >= pageTable.length)
	      return 0;
      
      int pAddr = getPaddr(vaddr);
      
	    int amount = Math.min(length, memory.length-pAddr);
	    System.arraycopy(memory, pAddr, data, offset, amount);

      unpinPage(vaddr);
	    return amount;
    }

    /* 
     * Takes a virtual address and returns the physical address that is
     * associated with it from the translation table.
     */
    private int getPaddr(int vaddr){
      int vpn = vaddr / pageSize;
      //System.out.println("Reading or Writing to: " + vpn);
      int offset = vaddr % pageSize;
      TranslationEntry entry = pageTable[vpn];
      int pPage = entry.ppn;
      return pPage * pageSize + offset;
    }


    /**
     * Transfer all data from the specified array to this process's virtual
     * memory.
     * Same as <tt>writeVirtualMemory(vaddr, data, 0, data.length)</tt>.
     *
     * @param	vaddr	the first byte of virtual memory to write.
     * @param	data	the array containing the data to transfer.
     * @return	the number of bytes successfully transferred.
     */
    public int writeVirtualMemory(int vaddr, byte[] data) {
	    return writeVirtualMemory(vaddr, data, 0, data.length);
    }


    /**
     * Transfer data from the specified array to this process's virtual memory.
     * This method handles address translation details. This method must
     * <i>not</i> destroy the current process if an error occurs, but instead
     * should return the number of bytes successfully copied (or zero if no
     * data could be copied).
     *
     * @param	vaddr	the first byte of virtual memory to write.
     * @param	data	the array containing the data to transfer.
     * @param	offset	the first byte to transfer from the array.
     * @param	length	the number of bytes to transfer from the array to
     *			virtual memory.
     * @return	the number of bytes successfully transferred.
     */
    public int writeVirtualMemory(int vaddr, byte[] data, int offset,
				                          int length) {
	    Lib.assertTrue(offset >= 0 && length >= 0 && offset+length 
                     <= data.length);
System.out.println("WRITING TO VirtualMemory at "+vaddr);
      checkAddr(vaddr/pageSize, true);
 	    pinPage(vaddr);

	    byte[] memory = Machine.processor().getMemory();
	    	
	    if (vaddr < 0 || vaddr / pageSize >= pageTable.length){
	      return 0;
      }

		  // all pages written to must not be read-only
		  for(int i=0; i <= length/pageSize; i++){
			  if(pageTable[(vaddr/pageSize)+i].readOnly == true){
				  return 0;
        }
      }

		  int pAddr = getPaddr(vaddr);
		  System.out.println("Which translates to paddr "+pAddr);
	    int amount = Math.min(length, memory.length-pAddr);
	    System.arraycopy(data, offset, memory, pAddr, amount);

	    unpinPage(vaddr);

	    return amount;
    }

    protected void checkAddr(int vpn, boolean write){
      //inentially empty
    }

    protected void pinPage(int vaddr){
	// intentionally empty
    }

    protected void unpinPage(int vaddr){
	// intentionally empty
    }


    /**
     * Load the executable with the specified name into this process, and
     * prepare to pass it the specified arguments. Opens the executable, reads
     * its header information, and copies sections and arguments into this
     * process's virtual memory.
     *
     * @param	name	the name of the file containing the executable.
     * @param	args	the arguments to pass to the executable.
     * @return	<tt>true</tt> if the executable was successfully loaded.
     */
    protected boolean load(String name, String[] args) {
	    Lib.debug(dbgProcess, "UserProcess.load(\"" + name + "\")");
		
	    OpenFile executable = ThreadedKernel.fileSystem.open(name, false);
	    if (executable == null) {
	      Lib.debug(dbgProcess, "\topen failed");
	      return false;
	    }

	    try {
	      coff = new Coff(executable);
	    }
	    catch (EOFException e) {
	      executable.close();
	      Lib.debug(dbgProcess, "\tcoff load failed");
	      return false;
	    }

	    // make sure the sections are contiguous and start at page 0
	    numPages = 0;
	    for (int s=0; s<coff.getNumSections(); s++) {
	      CoffSection section = coff.getSection(s);
	      if (section.getFirstVPN() != numPages) {
		      coff.close();
		      Lib.debug(dbgProcess, "\tfragmented executable");
		      return false;
	      }
	      numPages += section.getLength();
	    }

	    // make sure the argv array will fit in one page
	    byte[][] argv = new byte[args.length][];
	    int argsSize = 0;
	    for (int i=0; i<args.length; i++) {
	      argv[i] = args[i].getBytes();
	      // 4 bytes for argv[] pointer; then string plus one for null byte
	      argsSize += 4 + argv[i].length + 1;
	    }
	    if (argsSize > pageSize) {
	      coff.close();
	      Lib.debug(dbgProcess, "\targuments too long");
	      return false;
	    }

	    // program counter initially points at the program entry point
	    initialPC = coff.getEntryPoint();	

	    // next comes the stack; stack pointer initially points to top of it
	    numPages += stackPages;
	    initialSP = numPages*pageSize;

	    // and finally reserve 1 page for arguments
	    numPages++;
	
	    if (!loadSections()){
	      return false;
      }

	    // store arguments in last page
	    int entryOffset = (numPages-1)*pageSize;
	    int stringOffset = entryOffset + args.length*4;

	    this.argc = args.length;
	    this.argv = entryOffset;
	
	    for (int i=0; i<argv.length; i++) {
	      byte[] stringOffsetBytes = Lib.bytesFromInt(stringOffset);
	      Lib.assertTrue(writeVirtualMemory(entryOffset,stringOffsetBytes) == 4);
	      entryOffset += 4;
	      Lib.assertTrue(writeVirtualMemory(stringOffset, argv[i]) ==
		                                      argv[i].length);
	      stringOffset += argv[i].length;
	      Lib.assertTrue(writeVirtualMemory(stringOffset,new byte[] { 0 }) == 1);
	      stringOffset += 1;
	    }

	    return true;
    }


    /**
     * Allocates memory for this process, and loads the COFF sections into
     * memory. If this returns successfully, the process will definitely be
     * run (this is the last step in process initialization that can fail).
     *
     * @return	<tt>true</tt> if the sections were successfully loaded.
     */
    protected boolean loadSections() {
      //System.out.println("Pages required: " + numPages);
      pagesLock.acquire();
	    
      if (numPages > UserKernel.pagesAvailable.size()) {
	      coff.close();
        System.out.println("Insufficient physical memory");
	      Lib.debug(dbgProcess, "\tinsufficient physical memory");
	      return false;
	    }

      pageTable = new TranslationEntry[numPages];
      for (int i=0; i<numPages; i++){
        Integer nextAvailable = new Integer(-1);
        try{
          nextAvailable = UserKernel.pagesAvailable.remove();	  
        }
        catch(NoSuchElementException e){
        }
	      pageTable[i] = new TranslationEntry(i,
                       nextAvailable.intValue(), 
                       true,false,false,false);
      }

	    pagesLock.release();
	    
      // load sections
      for (int s=0; s<coff.getNumSections(); s++) {
	      CoffSection section = coff.getSection(s);
	    
	      Lib.debug(dbgProcess, "\tinitializing " + section.getName()
		              + " section (" + section.getLength() + " pages)");

	      for (int i=0; i<section.getLength(); i++) {
	        int vpn = section.getFirstVPN()+i;
          int pAddr = getPaddr(vpn);

	        // for now, just assume virtual addresses=physical addresses
          section.loadPage(i, pageTable[vpn].ppn);

          if(section.isReadOnly()){
            pageTable[vpn].readOnly = true;
          }
	      }
      }
	
	    return true;
    }


    /**
     * Release any resources allocated by <tt>loadSections()</tt>.
     */
    protected void unloadSections() {

      if(KThread.currentThread() instanceof UThread){
        pagesLock.acquire();

        for(int i = 0; i < fileDescriptor.length; ++i){
          handleClose(i);
        }
        for(int i = 0; i < pageTable.length; ++i){
         UserKernel.pagesAvailable.add(new Integer(pageTable[i].ppn));
        }
        pageTable = null;
	
	      pagesLock.release();
	
      }

    }    


    /**
     * Initialize the processor's registers in preparation for running the
     * program loaded into this process. Set the PC register to point at the
     * start function, set the stack pointer register to point at the top of
     * the stack, set the A0 and A1 registers to argc and argv, respectively,
     * and initialize all other registers to 0.
     */
    public void initRegisters() {
	    Processor processor = Machine.processor();

	    // by default, everything's 0
	    for (int i=0; i<processor.numUserRegisters; i++){
	      processor.writeRegister(i, 0);
      }
      //System.out.println("InitialPC: " + initialPC);
      //System.out.println("InitialSP: " + initialSP);   
	    // initialize PC and SP according
	    processor.writeRegister(Processor.regPC, initialPC);
	    processor.writeRegister(Processor.regSP, initialSP);

	    // initialize the first two argument registers to argc and argv
	    processor.writeRegister(Processor.regA0, argc);
	    processor.writeRegister(Processor.regA1, argv);
    }


    /**
     * Handle the halt() system call. 
     */
    private int handleHalt() {
	    if(PID == 0){
		    Machine.halt();
		    Lib.assertNotReached("Machine.halt() did not halt machine!");
	    }
	    return 0;
    }


    /**
     * Handle the open() system call. 
     */
    private int handleOpen(int nameAddress) {
      UserProcess uThread = UserKernel.currentProcess();
      if(uThread != null){
	      if(nameAddress < 0){
		      return -1;
        }
	      byte [] name = new byte[257];
	      int numbOfBytes;
        
        //System.out.println("Got to Open");
	      numbOfBytes = readVirtualMemory(nameAddress, name);
        //System.out.println("Got past Open");
	      if(numbOfBytes == 0){
		      return -1;
        }
      	String strName = new String(name);
	      strName = strName.split("\0")[0];

	      if(strName.length() == 257){
		      return -1;
        }

   	    // unlink has been called
	      if(tryingToCloseFile(strName.trim())){
		      return -1;
        }

	      FileSystem fs = Machine.stubFileSystem();
	    
	      OpenFile file = fs.open(strName.trim(), false);

	      if(file == null){
		      return -1;
        }

	      int fileDescriptorIndex = insertFile(file);


	      return fileDescriptorIndex;
      }

      return -1;
    }


  /**
	 * Attempt to read up to count bytes into buffer from the file or stream
	 * referred to by fileDescriptor.
	 *
	 * On success, the number of bytes read is returned. If the file descriptor
	 * refers to a file on disk, the file position is advanced by this number.
	 *
	 * It is not necessarily an error if this number is smaller than the number of
	 * bytes requested. If the file descriptor refers to a file on disk, this
	 * indicates that the end of the file has been reached. If the file descriptor
	 * refers to a stream, this indicates that the fewer bytes are actually
	 * available right now than were requested, but more bytes may become available
	 * in the future. Note that read() never waits for a stream to have more data;
	 * it always returns as much as possible immediately.
	 *
	 * On error, -1 is returned, and the new file position is undefined. This can
	 * happen if fileDescriptor is invalid, if part of the buffer is read-only or
	 * invalid, or if a network stream has been terminated by the remote host and
	 * no more data is available.
	 */
    private int handleRead(int fileDescriptorIndex, int buffer, int count) {
      UserProcess uThread = UserKernel.currentProcess();
      if(uThread != null){
	      if(count < 0){
		      return -1;
        }

 	      byte [] buff = new byte[count];

	      // check for errors	
	      if(fileDescriptorIndex > 15 || fileDescriptorIndex < 0){
		      return -1;
        }
	      if(fileDescriptor[fileDescriptorIndex] == null){
		      return -1;
        }

	      // get fileSystem Object
	      OpenFile file = fileDescriptor[fileDescriptorIndex];

	      if(file == null){
		      return -1;
        }

	      int offset = 0;
	
 	      int bytesReturned = 0;
	
	      bytesReturned = file.read(buff,bytesReturned,count);

	      if(bytesReturned == -1){
		      return -1;	
        }

	      return writeVirtualMemory(buffer, buff, 0, bytesReturned);
      }

      return -1;
    }


     /**
     * Handle the write() system call. 
     */
    private int handleWrite(int fileDescriptorIndex, int buffer, int count) {

	    if(count < 0){
		    return -1;
      }

 	    byte [] buff = new byte[count];
	    int numbOfBytes;

	    numbOfBytes = readVirtualMemory(buffer, buff, 0, count);
	    if(numbOfBytes == 0){
	      return -1;	
	    }
	
      // check for errors	
	    if(fileDescriptorIndex > 15 || fileDescriptorIndex < 0){
		    return -1;
      }
	    if(fileDescriptor[fileDescriptorIndex] == null){
		    return -1;
      }

	    // get fileSystem Object
	    OpenFile file = fileDescriptor[fileDescriptorIndex];

	    if(file == null){
		    return -1;
      }

	    int offset = 0;
	    int bytesReturned = 0;

	    bytesReturned = file.write(buff,offset,count);

	    if(bytesReturned == -1 || bytesReturned < count){
		    return -1;	
	    }	

	    return bytesReturned;
    }


    private int handleCreate(int nameAddress){
      UserProcess uThread = UserKernel.currentProcess();
      if(uThread != null){
	      if(nameAddress < 0){
		      return -1;
        } 

	      byte [] name = new byte[257];
	      int numbOfBytes;

	      numbOfBytes = readVirtualMemory(nameAddress, name);
	      if(numbOfBytes == 0){
		      return -1;
        }

	      String strName = new String(name);
	      strName = strName.split("\0")[0];

	      if(strName.length() == 257){
		      return -1;
        }

   	    // unlink has been called
	      if(tryingToCloseFile(strName.trim())){
		      return -1;
        }

	      FileSystem fs = Machine.stubFileSystem();

	      OpenFile file = fs.open(strName.trim(), true);

	      int fileDescriptorIndex = insertFile(file);

	      if(file == null){
		      return -1;
        }

	      return fileDescriptorIndex;
      }
      
      return -1;
    }


    private int handleClose(int fileDescriptorIndex){

	    // check for errors	
	    if(fileDescriptorIndex > 15 || fileDescriptorIndex < 0){
		    return -1;
      }
	    if(fileDescriptor[fileDescriptorIndex] == null){
		    return -1;
      }

	    int fCount = fileCount(fileName[fileDescriptorIndex]);
      
	    fileDescriptor[fileDescriptorIndex].close(); // close file

	    // if unlink attempted and last file 
	    if(attemptedUnlink[fileDescriptorIndex]){
		    if(fCount == 1){
			    FileSystem fs = Machine.stubFileSystem();
		      if(!fs.remove(fileName[fileDescriptorIndex].trim())){
				    return -1;
          }
		    }
	    }

	    fileDescriptor[fileDescriptorIndex] = null;
	    attemptedUnlink[fileDescriptorIndex] = false;
      fileName[fileDescriptorIndex] = null;
	
	    return 0;
    }


    private int handleUnlink(int nameAddress){
	    byte [] name = new byte[257];
	    int numbOfBytes;

	    numbOfBytes = readVirtualMemory(nameAddress, name);
	    if(numbOfBytes == 0){
	  	  return -1;
      }

      String strName = new String(name);
	    strName = strName.split("\0")[0];

	    if(strName.length() == 257){
		    return -1;
      }

      // If no processes have the file open
	    if(!inFileDescriptor(strName.trim())){
		    //delete immediately
		    FileSystem fs = Machine.stubFileSystem();

	      if(!fs.remove(strName.trim())){
			    return -1;
        }
	    }
	    // Else
	    else{
		  // file will remain in existence until the last file descriptor 
		  // referring to it is closed
		  setAttemptedUnlink(strName.trim());
	    }
	    return 0;
    }


	/**
	 * Terminate the current process immediately. Any open file descriptors
	 * belonging to the process are closed. Any children of the process no longer
	 * have a parent process.
	 *
	 * status is returned to the parent process as this process's exit status and
	 * can be collected using the join syscall. A process exiting normally should
	 * (but is not required to) set status to 0.
	 *
	 * exit() never returns.
	 */
    private void handleExit(int status){
      if(KThread.currentThread() instanceof UThread){
        pagesLock.acquire();
        UThread uThread = (UThread) KThread.currentThread();
        for(int i = 0; i < fileDescriptor.length; ++i){
          handleClose(i);
        }
        for(int i = 0; i < pageTable.length; ++i){
         UserKernel.pagesAvailable.add(new Integer(pageTable[i].ppn));
        }
        pageTable = null;
	
	      pagesLock.release();
	
        //Testing kernel's terminate method		
		    if(UserKernel.processCount == 1){
			    Kernel.kernel.terminate();
		    }

		    wakeParent(status);
		    UserKernel.processCount--;
        uThread.finish();
      }
    }
	
	
	  /**
	  * Execute the program stored in the specified file, with the specified
	  * arguments, in a new child process. The child process has a new unique
	  * process ID, and starts with stdin opened as file descriptor 0, and stdout
	  * opened as file descriptor 1.
	  *
	  * file is a null-terminated string that specifies the name of the file
	  * containing the executable. Note that this string must include the ".coff"
	  * extension.
	  *
	  * argc specifies the number of arguments to pass to the child process. This
	  * number must be non-negative.
	  *
	  * argv is an array of pointers to null-terminated strings that represent the
	  * arguments to pass to the child process. argv[0] points to the first
	  * argument, and argv[argc-1] points to the last argument.
	  *
	  * exec() returns the child process's process ID, which can be passed to
	  * join(). On error, returns -1.
    */
    private int handleExec(int file, int argc, int argv){
	
		  if(file < 0 || argc < 0 || argv < 0){
			  return -1;
      }
	
		  if(KThread.currentThread() instanceof UThread){
			  UThread uThread = (UThread) KThread.currentThread();
					
			  UserProcess process = UserProcess.newUserProcess();
			  process.pagesLock = pagesLock;
			  process.pidLock = pidLock;
			  passIPTLock(process);
			  // get filename
			  String filename = readVirtualMemoryString(file, 256);
			
			  if(filename == null){
				  return -1;
        }
			
			  // get string array
			  String [] argvArray = new String [argc];						
			
			  int offset = 0;
			  byte [] pointerBytes = new byte[4];
			  int pointer;
			  int bytesRead;
			
			  for(int i=0; i<argc; i++){			
					
			    pointer = 0;
			    bytesRead = readVirtualMemory(argv+4*i, pointerBytes); // get ith pointer
			    if(bytesRead != 4){
			  	  return -1;
          }
				
	  		  // reconstruct int from bytes
			    for(int j=0;j<pointerBytes.length;j++){
			 	    pointer += (int) pointerBytes[j]<<8*j;
			    }
				
  			  argvArray[i] = readVirtualMemoryString(pointer, 256); // get string at pointer
				
			    if(argvArray[i] == null){
			 	    return -1;
          }
			  }
			
			  boolean response = process.execute(filename, argvArray);
			
			  if(!response){
				  return -1;
		  	}

			  childProcess = process;
			  pidLock.acquire();
				UserKernel.processCount++;
				process.PID = UserKernel.uniquePID++;
			  pidLock.release();
			  childPID = process.PID;
			  process.parentProcess = this;
        process.myException = this.childException;
			  return process.PID;
      }

	    return -1;
    }
	

	/**
	 * Suspend execution of the current process until the child process specified
	 * by the processID argument has exited. If the child has already exited by the
	 * time of the call, returns immediately. When the current process resumes, it
	 * disowns the child process, so that join() cannot be used on that process
	 * again.
	 *
	 * processID is the process ID of the child process, returned by exec().
	 *
	 * status points to an integer where the exit status of the child process will
	 * be stored. This is the value the child passed to exit(). If the child exited
	 * because of an unhandled exception, the value stored is not defined.
	 *
	 * If the child exited normally, returns 1. If the child exited as a result of
	 * an unhandled exception, returns 0. If processID does not refer to a child
	 * process of the current process, returns -1.
	 */
    private int handleJoin(int processID, int statusPointer){
	
		  if(statusPointer < 0 || processID < 0){
        return -1;
      }
	
		  if(KThread.currentThread() instanceof UThread){
			  UThread uThread = (UThread) KThread.currentThread();
					
			  // has child with PID
			  if(childPID == processID){
			
				sleepLock.acquire();	
				// child has not exited
				if(childsExitStatus == -1){				
					childDone.sleep();
				}
				sleepLock.release();

					
				// get child status
				byte [] statusByte = new byte[4];

				// deconstruct int into bytes
			 	for(int j=0;j<statusByte.length;j++){
			 	   statusByte[j] = (byte) (childsExitStatus>>8*j);
			 	}

				int numbOfBytes;

				numbOfBytes = writeVirtualMemory(statusPointer, statusByte);

        if(numbOfBytes != 4)
					return -1;

				// disowned!
				childProcess = null;
				childPID = -1;
				
				if(childException == false)
					return 1;	
				else
					return 0;
		  }
		}
		
		return -1;
	}
	

	  /* Wakes up parent process, passing it exit status */
	  private void wakeParent(int status){
		  if(parentProcess != null){
		  	 parentProcess.sleepLock.acquire();
		   	 parentProcess.childsExitStatus = status;
		     parentProcess.childDone.wake();
		  	 parentProcess.sleepLock.release();
		  }	
	  }


    private int insertFile(OpenFile f){
	    for(int i = 0; i < fileDescriptor.length; i++){
		    if(fileDescriptor[i] == null){
			    fileDescriptor[i] = f;
			    fileName[i] = f.getName().trim();
			    attemptedUnlink[i] = false;
			    return i;
		    }
	    }

	    return -1;
    }


    private boolean tryingToCloseFile(String f){
	    for(int i = 0; i < fileName.length; i++){
		    if(fileName[i] != null && fileName[i].equals(f) 
           && attemptedUnlink[i] == true){
			    return true;
		    }
	    }

	    return false;
    }


    private boolean inFileDescriptor(String f){
	    for(int i = 0; i < fileDescriptor.length; i++){
		    if(fileDescriptor[i] != null && fileDescriptor[i].getName().equals(f)){
			    return true;
		    }
	    }

	    return false;
    }


    private void setAttemptedUnlink(String f){
	    for(int i = 0; i < fileName.length; i++){
		    if(fileName[i] != null &&  fileName[i].equals(f)){
			    attemptedUnlink[i] = true;
		    }
	    }
    }

    private int fileCount(String f){
	    int count = 0;
	    for(int i = 0; i < fileName.length; i++){
		    if(fileName[i] != null && fileName[i].equals(f)){
			    count++;
		    }
	    }
	    return count;
    }


    private static final int
      syscallHalt = 0,
	    syscallExit = 1,
	    syscallExec = 2,
	    syscallJoin = 3,
	    syscallCreate = 4,
	    syscallOpen = 5,
	    syscallRead = 6,
	    syscallWrite = 7,
	    syscallClose = 8,
	    syscallUnlink = 9;

    /**
     * Handle a syscall exception. Called by <tt>handleException()</tt>. The
     * <i>syscall</i> argument identifies which syscall the user executed:
     *
     * <table>
     * <tr><td>syscall#</td><td>syscall prototype</td></tr>
     * <tr><td>0</td><td><tt>void halt();</tt></td></tr>
     * <tr><td>1</td><td><tt>void exit(int status);</tt></td></tr>
     * <tr><td>2</td><td><tt>int  exec(char *name, int argc, char **argv);
     * 								</tt></td></tr>
     * <tr><td>3</td><td><tt>int  join(int pid, int *status);</tt></td></tr>
     * <tr><td>4</td><td><tt>int  creat(char *name);</tt></td></tr>
     * <tr><td>5</td><td><tt>int  open(char *name);</tt></td></tr>
     * <tr><td>6</td><td><tt>int  read(int fd, char *buffer, int size);
     *								</tt></td></tr>
     * <tr><td>7</td><td><tt>int  write(int fd, char *buffer, int size);
     *								</tt></td></tr>
     * <tr><td>8</td><td><tt>int  close(int fd);</tt></td></tr>
     * <tr><td>9</td><td><tt>int  unlink(char *name);</tt></td></tr>
     * </table>
     * 
     * @param	syscall	the syscall number.
     * @param	a0	the first syscall argument.
     * @param	a1	the second syscall argument.
     * @param	a2	the third syscall argument.
     * @param	a3	the fourth syscall argument.
     * @return	the value to be returned to the user.
     */
    public int handleSyscall(int syscall, int a0, int a1, int a2, int a3) {
	    switch (syscall) {
	      case syscallHalt:
	        return handleHalt();

	      case syscallRead:
	        return handleRead(a0,a1,a2);

	      case syscallOpen:
	        return handleOpen(a0);

	      case syscallCreate:
		      return handleCreate(a0);

	      case syscallClose:
		      return handleClose(a0);

	      case syscallUnlink:
		      return handleUnlink(a0);

	      case syscallExit:
		      handleExit(a0);

	      case syscallWrite:
	        return handleWrite(a0,a1,a2);
		
	      case syscallExec:
		      return handleExec(a0,a1,a2);

	      case syscallJoin:
		      return handleJoin(a0,a1);
		
	      default:
	        Lib.debug(dbgProcess, "Unknown syscall " + syscall);
	        Lib.assertNotReached("Unknown system call!" + syscall);
	    }
	    
      return 0;
    }


    /**
     * Handle a user exception. Called by
     * <tt>UserKernel.exceptionHandler()</tt>. The
     * <i>cause</i> argument identifies which exception occurred; see the
     * <tt>Processor.exceptionZZZ</tt> constants.
     *
     * @param	cause	the user exception that occurred.
     */
    public void handleException(int cause) {
	    Processor processor = Machine.processor();
	    switch (cause) {
	      case Processor.exceptionSyscall:
	        int result = handleSyscall(processor.readRegister(Processor.regV0),
				       processor.readRegister(Processor.regA0),
				       processor.readRegister(Processor.regA1),
				       processor.readRegister(Processor.regA2),
				       processor.readRegister(Processor.regA3)
				       );
	        processor.writeRegister(Processor.regV0, result);
	        processor.advancePC();
	        break;				       
				       
	      default:
          System.out.println("Exception: " + Processor.exceptionNames[cause]);
		unloadSections();
	        myException = true;
	        Lib.debug(dbgProcess, "Unexpected exception: " +
		      Processor.exceptionNames[cause]);
	        Lib.assertNotReached("Unexpected exception");
      }
    }

    /** The program being run by this process. */
    protected Coff coff;

    /** This process's page table. */
    protected TranslationEntry[] pageTable;
    /** The number of contiguous pages occupied by the program. */
    protected int numPages;

    /** The number of pages in the program's stack. */
    protected final int stackPages = 8;
    
    protected int initialPC, initialSP;
    protected int argc, argv;
	
    protected static final int pageSize = Processor.pageSize;
    private static final char dbgProcess = 'a';
    private OpenFile [] fileDescriptor = new OpenFile[16];
    private String [] fileName = new String[16];
    private boolean [] attemptedUnlink = new boolean[16];
	  public int PID;
	  private int childPID;
	  public UserProcess childProcess;
	  public UserProcess parentProcess;
	  public int childsExitStatus = -1;
    public boolean childException = false;
    public boolean myException = false;
	  public Lock sleepLock = new Lock();
	  public Condition childDone = new Condition(sleepLock);
	  public Lock pagesLock;
	  public Lock pidLock;
}
